<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
 function createIterable(iterations) {
   return {
     [Symbol.iterator]() {
       var i = 0;
       return {next: () => iterations[i++]};
     },
   };
 }

 test(() => {
   let sequenceTest = internals.sequenceTest();

   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(null) },
                    "Converting null to sequence must throw a type error");
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(undefined) },
                    "Converting undefined to sequence must throw a type error");
   assert_equals(sequenceTest.identityOctetSequenceOrNull(null), null,
                 "Converting null to a nullable sequence works");
   assert_equals(sequenceTest.identityOctetSequenceOrNull(undefined), null,
                 "Converting undefined to a nullable sequence works");
 }, "null and undefined conversions");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let emptyArray = [];
   let emptyObject = createIterable([{done: true}]);

   let convertedArray = sequenceTest.identityLongSequence(emptyArray);
   assert_array_equals(convertedArray, [], "Empty array produces an empty vector");
   let convertedObject = sequenceTest.identityLongSequence(emptyObject);
   assert_array_equals(convertedObject, [], "Empty object produces an empty vector");
 }, "Empty sequences");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let obj = createIterable([
     { done: false, value: 34 },
     { done: false, value: 42 },
     { done: true }
   ]);
   let convertedObj = sequenceTest.identityLongSequence(obj);
   assert_array_equals(convertedObj, [34, 42],
                       "'done: true' does not need a value property");

   obj = createIterable([
     { done: false, value: 34 },
     { done: false, value: 42 },
     { done: true, value: 88 }
   ]);
   convertedObj = sequenceTest.identityLongSequence(obj);
   assert_array_equals(convertedObj, [34, 42],
                       "'value' is ignored when 'done' is true");

   obj = createIterable([
     { done: 0, value: 42 },
     { done: 1, value: 34 }
   ]);
   convertedObj = sequenceTest.identityDoubleSequence(obj);
   assert_array_equals(convertedObj, [42],
                       "'done' is always converted to a boolean");
 }, "Iterator stop values");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let array = [1, 2];
   let convertedArray = sequenceTest.identityDoubleSequence(array);
   assert_not_equals(array, convertedArray);
   convertedArray.push(42);
   let convertedArray2 = sequenceTest.identityDoubleSequence(array);
   assert_equals(array.length, 2);
   assert_not_equals(convertedArray, convertedArray2);
   assert_array_equals(convertedArray2, [1, 2]);
 }, "Sequences are passed by value");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let longArray = ['not', 'a', 'long'];
   longArray[5] = 42;
   let convertedLongArray = sequenceTest.identityLongSequence(longArray);
   assert_array_equals(convertedLongArray, [0, 0, 0, 0, 0, 42],
                       "Long array with gaps correctly produces a vector");

   let doubleArray = [34, 42.5];
   doubleArray[9] = 2;  // The elements inbetween are all undefined.
   assert_throws_js(TypeError, () => { sequenceTest.identityDoubleSequence(doubleArray) },
                    "Converting an undefined value to double throws a TypeError and " +
                    "causes the sequence -> C++ conversion to fail");

   let byteStringSequenceSequence = [
     ["foo"],
     ["bar"]
   ];
   byteStringSequenceSequence[7] = ["baz"];
   assert_throws_js(TypeError, () => {
     sequenceTest.identityByteStringSequenceSequence(byteStringSequenceSequence);
   }, "Converting an undefined to a sequence<> throws a TypeError and causes the " +
      "entire conversion to fail");
 }, "Arrays with gaps in the elements");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence({}) },
                    "Objects without Symbol.iterator cannot be converted");

   let obj = {
     [Symbol.iterator]: 42
   };
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(obj) },
                    "Symbol.iterator must be callable");

   obj = {
     [Symbol.iterator]() {
       return 42;
     }
   }
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(obj) },
                    "Symbol.iterator must return an object");

   obj = {
     [Symbol.iterator]() {
       return {}
     }
   }
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(obj) },
                    "Symbol.iterator must return an object with a 'next' callable");

   obj = {
     [Symbol.iterator]() {
       return {'next': 42}
     }
   }
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(obj) },
                    "Symbol.iterator must return an object with a 'next' callable");

   obj = {
     [Symbol.iterator]() {
       return {'next': () => { return 42 }}
     }
   }
   assert_throws_js(TypeError, () => { sequenceTest.identityLongSequence(obj) },
                    "'next' must return an object");
 }, "Invalid iterable protocols");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   assert_false(sequenceTest.unionReceivedSequence(42),
                "Passing a double should convert the union to double");
   assert_false(sequenceTest.unionReceivedSequence(true),
                "Passing a boolean should convert the union to double");
   assert_true(sequenceTest.unionReceivedSequence([]),
               "Passing an empty array should convert the union to a sequence");
   assert_true(sequenceTest.unionReceivedSequence([34, 42]),
               "Passing an array should convert the union to a sequence");
   let obj = createIterable([{done: false, value: 99},
                             {done: false, value: -3.14},
                             {done: true}]);
   assert_true(sequenceTest.unionReceivedSequence(obj),
               "Passing an iterable object should convert the union to a sequence");
 }, "Sequences in unions");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let foods = ["Sushi", "Beer"];
   assert_throws_js(TypeError, () => { sequenceTest.identityFoodEnumSequence(foods) },
                    "Invalid enum type must throw a type error");

   foods = createIterable([{done: false, value: "Spaghetti"},
                           {done: false, value: "Bread"},
                           {done: true}]);
   assert_array_equals(sequenceTest.identityFoodEnumSequence(foods),
                       ["Spaghetti", "Bread"]);
 }, "Enum sequences");

 test(() => {
   let sequenceTest = internals.sequenceTest();

   let sequenceCreationFunctions = [
     (elem) => { return [elem] },
     (elem) => { return createIterable([{done: false, value: elem},
                                        {done: true}]) }
   ];
   for (sequenceCreationFunction of sequenceCreationFunctions) {
     let elem = document.createElement('p');
     sequenceTest.setElementSequence(sequenceCreationFunction(elem));
     assert_array_equals(sequenceTest.getElementSequence(), [elem],
                         "The same DOM object was stored in the sequence");
     assert_not_equals(sequenceTest.getElementSequence()[0],
                       document.createElement('br'),
                       "The same DOM object was stored in the sequence");

     elem = document.createElement('br');
     assert_not_equals(sequenceTest.getElementSequence()[0], elem,
                       "Changing the original object does not change the sequence value");
   }
 }, "Converting DOM object sequences");

 test(() => {
   const sequenceTest = internals.sequenceTest();

   const array = [
     1,
     {valueOf: () => { array.pop(); return 2; }},
     3
   ];
   assert_array_equals(
     sequenceTest.identityLongSequence(array),
     [1, 2],
     "Array iteration should respect new length");
 }, "Array to sequence<> conversions with pop() during iteration");

 test(() => {
   const sequenceTest = internals.sequenceTest();

   const array = [
     1,
     {valueOf: () => { array.push(3); return 2; }},
   ];
   assert_array_equals(
     sequenceTest.identityLongSequence(array),
     [1, 2, 3],
     "Array iteration should respect new length");
 }, "Array to sequence<> conversions with push() during iteration");

 test(() => {
   const sequenceTest = internals.sequenceTest();

   const array = [
     1,
     {valueOf: () => { array.length = 3; return 2; }}
   ];
   assert_array_equals(
     sequenceTest.identityLongSequence(array),
     [1, 2, 0],
     "Array iteration should respect new length");
 }, "Array to sequence<> conversions with length increase during iteration");

 test(() => {
   const sequenceTest = internals.sequenceTest();

   const array = [
     1,
     {valueOf: () => { array.length = 2; return 2; }},
     3
   ];
   assert_array_equals(
     sequenceTest.identityLongSequence(array),
     [1, 2],
     "Array iteration should respect new length");
 }, "Array to sequence<> conversions with length decrease during iteration");

</script>
